/**
 * @author renato
 * Created on 28/09/2005
 * updated on 02/04/2010
 */
package br.unb.core.message;

import br.unb.core.synchronism.Synchronism;
import br.unb.core.traffic.Traffic;
import br.unb.plugins.geral.view.XmlFactory;

public class Message {

    public enum Type {

        /**
         * (From any to any)
         * Message used on chat panel comunication between two hosts
         */
        DIALOG,
        /**
         * (From controller to receiver.)
         * Signals to create a listening thread on a port
         */
        PREPARE_TO_RECEIVE,
        /**
         * (From controller to receiver.)
         * Signals to sleep the listening thread
         */
        STOP_RECEIVER,
        /**
         * (From controller to receiver.)
         * Signals to kill the listening thread
         */
        DELETE_RECEIVER,
        /**
         * (From controller to sender.)
         * Signals to create the sender thread.
         */
        PREPARE_TO_SEND,
        /**
         * (From controller to sender.)
         * Signals the sending machine to wake up the thread and start sending packets.
         */
        START_SENDER,
        /**
         * (From controller to sender.)
         * Signals to sleep sender thread
         */
        STOP_SENDER,
        /**
         * (From controller to sender.)
         * Signals the sender machine to kill the sender thread.
         */
        DELETE_SENDER,
        /**
         * (From syncmanager to syncclient)
         * Mensage: xxx.xxx.xxx.xxx:yyyy  (IP:Port).
         * Sinchronizes with the server at IP/Port
         */
        START_SYNCCLIENT,
        /**
         * (From syncmanager to syncserver)
         * Mensage: xxx.xxx.xxx.xxx:yyyy  (IP:Port)
         * Used to know if this host is a synchronization server.
         * When received creates a Synchronization thread that listen at desired port.
         * It then sends a START_SYNCCLIENT message to the patner with the IP/port of this server.
         */
        START_SYNCSERVER,
        /**
         * (From syncmanager to syncClient)
         * Stops synchronism on a machine
         */
        STOP_SYNCCLIENT,
        /**
         * (From any to any)
         * Stops synchronism on a machine
         */
        STOP_SYNCSERVER,
        /**
         * (From Syncclient or syncserver to syncmanager )
         * Confirms sync request (wait_sinc ou sinc)
         */
        OK_SYNC,
        /**
         * From sender/receiver to controller
         * Error message, informs errors initiating worker threads and
         * problems like not beeing able to open ports.
         */
        ERROR,/**
         * (From any to any)
         * Message used to acknowledge the execution of an operation.
         * Send to carry a String with the operation results.
         * (The acknolegment of an operation is optional).
         */
        ACK;

        //TODO fix this using EnumMap
        static Type createFromByte(Byte b) {
            if (b == ACK.ordinal()) {
                return ACK;
            } else if (b == DIALOG.ordinal()) {
                return DIALOG;
            } else if (b == PREPARE_TO_RECEIVE.ordinal()) {
                return PREPARE_TO_RECEIVE;
            } else if (b == STOP_RECEIVER.ordinal()) {
                return STOP_RECEIVER;
            } else if (b == DELETE_RECEIVER.ordinal()) {
                return DELETE_RECEIVER;
            } else if (b == PREPARE_TO_SEND.ordinal()) {
                return PREPARE_TO_SEND;
            } else if (b == START_SENDER.ordinal()) {
                return START_SENDER;
            } else if (b == STOP_SENDER.ordinal()) {
                return STOP_SENDER;
            } else if (b == DELETE_SENDER.ordinal()) {
                return DELETE_SENDER;
            } else if (b == START_SYNCCLIENT.ordinal()) {
                return START_SYNCCLIENT;
            } else if (b == START_SYNCSERVER.ordinal()) {
                return START_SYNCSERVER;
            } else if (b == STOP_SYNCCLIENT.ordinal()) {
                return STOP_SYNCCLIENT;
            } else if (b == STOP_SYNCSERVER.ordinal()) {
                return STOP_SYNCSERVER;
            } else if (b == OK_SYNC.ordinal()) {
                return OK_SYNC;
            } else {
                return ERROR;
            }
        }
    }
    private Type type;
    /**
     * Will hold the decoded data,
     * For now, it can be a String or a Serialized Traffic class
     */
    private byte data[];

    Message() {
    }

    /******************************* Origin ********************************/
    /**
     * (Executed on Origin)
     * Used to build the message at origin
     * (gets an object and serialize it into a String, puts the result in this.data)
     * Tranforms the objetcs to be sent in a xml or String.
     * @param Type Type of message
     * @param object object to be send
     */
    public Message(Type type, Object object) {
        this.type = type;
        byte[] objectBytes;
        if (object instanceof Traffic) {
            objectBytes = XmlFactory.getXmlFromTraffic((Traffic) object).getBytes();
        } else if (object instanceof String) {
            objectBytes = (object.toString() + "\0").getBytes();
        } else {
            objectBytes = object.toString().getBytes();
        }
        data = new byte[(objectBytes.length + 1)];
        data[0] = (byte) this.type.ordinal();
        System.arraycopy(objectBytes, 0, data, 1, objectBytes.length);
    }

    /************************* Destination ***************************/
    /**
     * (Executed on Destination)
     * Used to decode the message at destination.
     * (gets this.data and decodes it storig result on this.object )
     * @param dataMessage Array to receive the packet.
     */
    public Message(byte[] dataMessage) {
        byte b = dataMessage[0];
        this.type = Type.createFromByte(b);
        this.data = dataMessage;
    }

    /**
     * (Executed on Destination)
     * Used to return the object of a mesage
     * @return
     */
    public Traffic getTraffic() {
        switch (this.type) {
            case PREPARE_TO_RECEIVE: //(XML) carries the whole traffic
            case PREPARE_TO_SEND: //   (XML) carries the whole traffic
            {
                /**
                 * Returns a traffic
                 */
                String xml = new String(this.data, 1, (this.data.length - 1));
                return (Traffic) XmlFactory.getTrafficFromXml(xml);
            }
        }
        return null;
    }

    /**
     * (Executed on Destination)
     * Used to return the object of a mesage
     * @return
     */
    public String getText() {
        switch (this.type) {
            case ACK:    //(String) carries the success message
            case DIALOG: //(String) carries dialog message
            case ERROR: // (String) carries the error message
            {
                return (String) decodeString();
            }
        }
        return null;
    }

    /**
     * (Executed on Destination)
     * Used to return the object of a mesage
     * @return
     */
    public String getTrafficID() {
        switch (this.type) {
            case STOP_RECEIVER:  //(String) carries the traffic name
            case DELETE_RECEIVER://(String) carries the traffic name
            case START_SENDER:   //(String) carries the traffic name
            case STOP_SENDER:    //(String) carries the traffic name
            case DELETE_SENDER: // (String) carries the traffic name
            {
                return decodeString();
            }
        }
        return null;
    }

    /**
     * (Executed on Destination)
     * Used to return the object of a mesage
     * @return
     */
    public Synchronism getSyncModel() {
        switch (this.type) {
            case START_SYNCCLIENT://(Synchronism) carries syncmodel data
            case STOP_SYNCCLIENT: //(Synchronism) empty
            case START_SYNCSERVER://(Synchronism) carries syncmodel data
            case STOP_SYNCSERVER: //(Synchronism) empty
            case OK_SYNC: //        (Synchronism) carries syncmodel data
            {
                /**
                 * Returns a sinchronization model
                 */
                return new Synchronism(new String(this.data, 1, (this.data.length - 1)));
            }
        }
        return null;
    }

    /************************* Utils ***************************/
    private String decodeString() {
        /**
         * Returns a string
         */
        int len;
        for (len = 1; len < this.data.length && this.data[len] != 0; len++) {
            ;//len ++, count character
        }
        len--;//get the length to the \0 at the end
        return new String(this.data, 1, len);
    }

    /**
     * (Executed on Origin)
     * Used to return the byte array of the message, contains the object and
     * the Type concatenated. Useful to encapsulate the data for a new message.
     * @return
     */
    byte[] getData() {
        return this.data;
    }

    public Type getType() {
        return this.type;
    }

    private String getTypeName() {
        return this.type.toString();
    }

    @Override
    public String toString() {
        switch (this.type) {
            case PREPARE_TO_RECEIVE: //(XML) carries the whole traffic
            case PREPARE_TO_SEND: //   (XML) carries the whole traffic
            {
                return this.getTypeName() + " " + this.getTraffic().getName() + " (xml contents)";
            }//break;
            case ACK:            //(String) carries the success message
            case DIALOG:         //(String) carries dialog message
            case ERROR: //         (String) carries the error message
            {
                return this.getTypeName() + " " + this.getText();
            }//break;
            case STOP_RECEIVER:  //(ID) carries the traffic name
            case DELETE_RECEIVER://(ID) carries the traffic name
            case START_SENDER:   //(ID) carries the traffic name
            case STOP_SENDER:    //(ID) carries the traffic name
            case DELETE_SENDER: // (ID) carries the traffic name
            {
                return this.getTypeName() + " " + this.getTrafficID();
            }//break;
            case START_SYNCCLIENT://(Synchronism) carries syncmodel data
            case STOP_SYNCCLIENT: //(Synchronism) empty
            case START_SYNCSERVER://(Synchronism) carries syncmodel data
            case STOP_SYNCSERVER: //(Synchronism) empty
            case OK_SYNC: //        (Synchronism) carries syncmodel data
            {
                return this.getTypeName() + " " + this.getSyncModel().toString();
            }//break;
            default: //(null) dont know the message Type.
            {
                return "UNKNOW " + this.getTypeName();
            }
        }
    }

    /**
     * Verify if some error ocurred on remote host
     * @param m answer received
     */
    public static boolean verifyError(Message m) {
        if (m == null) {
            return true;
        } else if (m.getType() == Type.ERROR) {
            return true;
        }
        return false;

    }
}
